"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.service.utils import Hash
from msnp.service import network
from msnp.service.network.sending import Sender
from msnp.logger import Logger
import traceback
import time
from msnp.service.network import logic, BackupController, NetworkLock
from msnp.service.network.model.ReliableTransportModel import RTRehelloMessage, \
    RTLeaveMessage, RTFinishCrashMessage
from msnp.service.network import sending
from msnp.model import PTPModel

##############################
## FINGERTABLE CALCULATIONS ##
##############################


def calculateFinger(hashedNameLong, i, isMinus):
    """This function calculates a finger.
    
    If isMinus is False it calculates the peer the i-th finger of the peer 'hashedNameLong' point to.
    If isMinus is True it calculates the peer that points with the i-th finger to the peer 'hashedNameLong'.
    
    Args:
        hashedNameLong: the hashed name (long).
        i: the i-th finger (integer).
        isMinus: a boolean
        
    Returns:
        The calculated finger as long.
    """
    
    #hex value of new finger
    m = Hash.getHashRange()
    value = 0L
    if isMinus:
        value = hashedNameLong - (2 ** i)
    else:
        value = hashedNameLong + (2 ** i)
    #modulo the hash range
    value = value % (2 ** m)
    return value


def calculateFingerRangeImResponsibleForIdentity(identity):
    """This function calculates for which finger of a given identity this peer is responsible.
    
    Args:
        identity: the PeerInfo instance.
        
    Returns:
        Returns a tupple containing the lower and upper index.
    """
    
    lowerIndex = -1
    upperIndex = -1
    m = Hash.getHashRange()
    #if the predecessor is None in this call i have a problem :)
    if network.NetworkController.getMySelf().getPredecessor() != None:
        respIndexList = []
        predecessorHashedNameLong = network.NetworkController.getMySelf().getPredecessor().getHashedNameLong()
        myHashedNameLong = network.NetworkController.getMySelf().getMyIdentity().getHashedNameLong()
        for i in range(0, m):
            shouldBeFinger = calculateFinger(identity.getHashedNameLong(), i, False)
            if logic.ResponsibilityLogic.isResponsible(predecessorHashedNameLong, shouldBeFinger, myHashedNameLong):
                respIndexList.append(i)
        if len(respIndexList) > 0:
            lowerIndex = respIndexList[0]
            upperIndex = respIndexList[len(respIndexList) - 1]
    else:
        Logger.info("predecessor None in calculateFingerRangeImResponsibleForIdentity!!! Cannot happen")
    return [lowerIndex, upperIndex]


def recalculateFingertableJoin(joinIdentity):
    """This function recalculates the whole fingertable of the current peer if a new user joined.
    
    It looks for fingers that currently point to a bigger peer than the new peer 'joinIdentity'
     and check if this finger should point now to the 'joinedIdentity'.
    
    Args:
        joinIdentity: the PeerInfo instance of the new joining peer.
    """
    
    deletedFingers = []
    m = Hash.getHashRange()
    for i in range(0, m):
        myIdentity = network.NetworkController.getMySelf().getMyIdentity()
        shouldBeFinger = calculateFinger(myIdentity.getHashedNameLong(), i, False)
        finger = network.NetworkController.getMySelf().getFingerById(i)
        #only update finger if it pointed to a peer that is bigger than the joinIdentity -> finger points between joinIdentity and me
        if finger == None or logic.ResponsibilityLogic.isResponsible(joinIdentity.getHashedNameLong(), finger.getHashedNameLong(), myIdentity.getHashedNameLong()):
            #if joinIdentity is responsible for the finger -> finger should point between me and joinIdentity
            if logic.ResponsibilityLogic.isResponsible(myIdentity.getHashedNameLong(), shouldBeFinger, joinIdentity.getHashedNameLong()):
                connectedIdentities = network.NetworkController.getMySelf().getAllConnectedIdentities()
                if network.NetworkController.getMySelf().containsIdentity(connectedIdentities, joinIdentity) == False:
                    #shift backup to new finger
                    network.BackupController.insertAtFinger(joinIdentity)
                #set new finger
                Logger.info("  -> set finger (recalcJoin) " + str(i) + " to " + joinIdentity.getName())
                network.NetworkController.getMySelf().setFinger(joinIdentity, i)
                #buffer deleted fingers
                if deletedFingers.__contains__(finger) == False:
                    deletedFingers.append(finger)
    #to guarantee that the delete-request is transmitted after the updates       
    time.sleep(0.5)
    #delete backup from deleted fingers
    for delFinger in deletedFingers:
        network.BackupController.deleteFromFinger(delFinger)
            
            
def recalculateFingertableLeave(predecessor, leavedIdentity, successor, isCrash=False):
    """This function recalculates the fingertable if a user leaved.
    
    It looks for fingers that currently point to the successor of the leaving peer 'leaveIdentity' 
    and set them to the 'successorOfLeaveIdentity'.
    
    Args:
        predecessor: the PeerInfo instance of the predecessor.
        leavedIdentity: the PeerInfo instance of the leaving peer.
        successor: the PeerInfo instance of the successor.
        isCrash: This boolean determines if an underlying crash procedure is executed.
    """
    
    m = Hash.getHashRange()
    myIdentity = network.NetworkController.getMySelf().getMyIdentity()
    for i in range(0, m):
        #only update finger if it pointed to the leaveIdentity till now
        finger = network.NetworkController.getMySelf().getFingerById(i)
        #if finger and successor are already the same we don't have to do anything
        if finger == None or finger.getName() != successor.getName():
            reset = False
            shouldBeFinger = calculateFinger(myIdentity.getHashedNameLong(), i, False)
            predecessorHashedNameLong = predecessor.getHashedNameLong()
            successorHashedNameLong = successor.getHashedNameLong()
            #shouldbefinger must be in range predecessor..successor
            if logic.ResponsibilityLogic.isResponsible(predecessorHashedNameLong, shouldBeFinger, successorHashedNameLong):
                #leave
                if leavedIdentity != None:
                    #if finger points to leaved identity reset it
                    if finger == None or finger.getName() == leavedIdentity.getName():
                        reset = True
                    #do not reset finger if it points exactly to the shouldbefinger
                    elif finger.getHashedNameLong() != shouldBeFinger:
                        reset = logic.ResponsibilityLogic.isResponsible(shouldBeFinger, finger.getHashedNameLong(), successorHashedNameLong) == False
                #crash
                else:
                    # if current finger points beyond the successor reset it.
                    if finger != None and logic.ResponsibilityLogic.isResponsible(predecessorHashedNameLong, successorHashedNameLong, finger.getHashedNameLong()):
                        reset = True
                    #if current finger is better then successor, check whether it is alive (current finger points between predecessor and successor)
                    elif finger != None and logic.ResponsibilityLogic.isResponsible(predecessorHashedNameLong, shouldBeFinger, finger.getHashedNameLong()):
                        if Sender.isAlive(finger) == False:
                            reset = True
                    #otherwise we are in the range so we will reset the finger
                    else:
                        reset = True
            if reset:
                #shift backup from old finger to new finger
                network.BackupController.insertAtFinger(successor)
                #don't have to delete at old finger, he is logged out or killed!!!!
                Logger.info("  -> set finger (recalcLeave) " + str(i) + " to " + successor.getName())
                network.NetworkController.getMySelf().setFinger(successor, i)


def executeUpdatePredecessorOfSuccessor(identity, isCrash):
    """This function changes the predecessor of a successor.
    
    Sets the predecessor to the PeerInfo instance 'identity'.
    If it was a leave the backup of the old predecessor is now stored as the own backup.
    If it was a crash I search in my database for all backups I am now responsible of.
    
    Args:
        identity: the PeerInfo instance.
        isCrash: This boolean determines if an underlying crash procedure is executed.
        
    Returns:
        Returns SUCCESSFUL, RECONNECT, FORCE_REHELLO or UPDATE_PREDECESSOR_ERROR.
    """
    
    try:
        #set new predecessor and store old for a reset in case of an error
        oldPredecessor = network.NetworkController.getMySelf().getPredecessor()
        myIdentity = network.NetworkController.getMySelf().getMyIdentity()
        if isCrash:
            if oldPredecessor.getHashedNameLong() != identity.getHashedNameLong():
                #additional check if old predecessor is still alive
                if Sender.isAlive(oldPredecessor):
                    #if the new identity is bigger than the oldpredecessor -> it was excluded from the ring
                    if logic.ResponsibilityLogic.isResponsible(oldPredecessor.getHashedNameLong(), identity.getHashedNameLong(), myIdentity.getHashedNameLong()):
                        return PTPModel.ReturnType.RECONNECT
                    #in this case the crashtimer was to short
                    else:
                        #broadcast the rehello message
                        message = RTRehelloMessage(oldPredecessor, myIdentity)
                        #it makes no sense to sequentially forward this message because this message would reach the crashed identity
                        message.setUseBroadcast(True)
                        sending.ReliableNetworkExchange.initiateRTMessage(message)
                        #set new predecessor
                        network.NetworkController.getMySelf().setPredecessor(identity)
                        return PTPModel.ReturnType.FORCE_REHELLO
                else:
                    Logger.info("my predecessor crashed -> reset predecessor to " + identity.getName())
                    network.NetworkController.getMySelf().setPredecessor(identity)
                    #if there is a crash search if i have backups for the crashed identities... if so store them
                    #all other backups are received using a callback initiated from my new predecessor (predecessor of crashed identity)
                    backups = network.BackupController.selectBackupFromTo(identity.getHashedNameLong(), myIdentity.getHashedNameLong())
                    network.BackupController.processBackupCallback(backups)
                    #finish crash -> peers have to reset certain fingers + search for backups
                    message = RTFinishCrashMessage(identity, myIdentity, oldPredecessor)
                    sending.ReliableNetworkExchange.initiateRTMessage(message)
        #in case of a logout the backup from the old predecessor has to be set to own data
        else:
            #lock this because it could happen that a peer wants to upgrade before the backupcontent is set
            NetworkLock.acquireBackupLock()
            try:
                Logger.info("my predecessor " + oldPredecessor.getName() + " leaved the network, set it to " + identity.getName())
                network.NetworkController.getMySelf().setPredecessor(identity)
                #set backup from old predecessor as own data
                Logger.info("my predecessor leaved the network -> set his backup as my own and update fingers")
                BackupController.setBackupFromToNone(oldPredecessor.getName())
            finally:
                NetworkLock.releaseBackupLock()
            #initiate the leave message for the old predecessor
            #outside try-catch because in the leave-rt-function we have a backup-lock
            message = RTLeaveMessage(oldPredecessor, identity, myIdentity)
            sending.ReliableNetworkExchange.initiateRTMessage(message)
        return PTPModel.ReturnType.SUCCESSFUL
    except:
        Logger.info('exception while updating the predecessor')
        network.NetworkController.getMySelf().setPredecessor(oldPredecessor)
        Logger.info(traceback.format_exc())
        return PTPModel.ReturnType.UPDATE_PREDECESSOR_ERROR
    
    
def executeGetSuccessor():
    """This function returns the successor (PeerInfo instance) of the current peer.
        
    Returns:
        Returns the successor as PeerInfo instance.
    """
    
    return network.NetworkController.getMySelf().getSuccessor()
